/* 
 * ToDo:
 * 		1. make the binary in 2s compliment to allow neg numbers
 * 		2. write replaceMemoryLabel. Figure out how the fuck to make it work....
 * 		3. jump and link needs to store a value for $ra? The assembler does this?
 */


package maker;


import java.io.IOException;
import java.util.HashMap;

public class Assembly {

	public static HashMap<String, String> symbTable; 	// for storing labels and their addresses
	public static HashMap<String, String> regTable;		// matches register number to register
	public static HashMap<String, String> instTable;	// matches instruction to opcode

	public static String[] zeros={"","0","00","000","0000","00000","000000","0000000","00000000",
		"000000000", "0000000000", "00000000000", "00000000000",};

	/* WE ARE FUCKED */
	public static void main(String[] arg) throws IOException {

		// can put this in initialize maybe...
		Assembler assembler = new Assembler(arg) {

			/*
			 * The current program counter. You may need it while processing labels.
			 */

			@Override
			void updateProgramCounter(Instruction instruction) {
				programCounter += 1;
			}

			/*
			 * The method is called before the data memory is dumped to a $prefix_d.coe file.
			 * It will replace the data written in labels with immediate values.
			 */

			@Override
			void replaceMemoryLabel() {
				// call memory to modify crap in it.
				// .add(String data, int address), .find(int address), .print(), .dump(), .leng()

				// all of the memory management is handled for us by processData...
				// STILL NEED TO WRITE

				// go through memory entries and search for labels
				String s;
				int address;
				String data;
				
				System.out.println(memory.entries.length);
				for(int i = 0; i < memory.entries.length; i++)
				{
					if(memory.entries[i].data != null){
						if((s = getOperandType(memory.entries[i].data)).equalsIgnoreCase("label"))
						{
							address = Integer.valueOf(symbTable.get(s)).intValue();		// needs to be an int
							data = memory.find(address);
							memory.entries[i].data = data;
							memory.entries[i].address = address;
						}
					}
				} 
			}

			/*
			 * Replace the operands in the form of labels with immediate values.
			 * The method will be called before generating the machine code for an
			 * instruction in the AssembleCode method.
			 */

			@Override
			void replaceInstructionLabel(Instruction instruction) {
				String op_type;
				if (instruction.operator.equals("WORD") || instruction.operator.equals("FILL"))
				{ return; }
				for(int i = 0; i < instruction.operands.length; i++)
				{

					op_type = getOperandType(instruction.operands[i].name);
					// labels cannot have numbers as the first character
					if(op_type.equalsIgnoreCase("label") && !isDigit(instruction.operands[i].name.charAt(0)) &&
							instruction.operands[i].name.charAt(0) != '-')
					{
						// convert the label name to the address that the label actually corresponds to
						instruction.print();
						System.out.println("set " + instruction.operands[i].name + " to: " + 
								symbTable.get(instruction.operands[i].name).toString());
						instruction.operands[i].name = symbTable.get(instruction.operands[i].name).toString();

					}
				}
			}

			/*
			 * You have to implement it to process the labels.
			 * You may also need to implement a symbol table to
			 * maintain the labels and their real addresses.
			 */

			@Override
			void processLabel(String sourceCode) {
				if(currentCodeSection == 0)
				{
					//System.err.println("placing " + sourceCode + " at pc: " + programCounter);
					symbTable.put(sourceCode, Integer.toBinaryString(programCounter-1)); 
				}
				// might be wrong
				else
				{
					System.out.println("storing " + sourceCode + " in spot " + dataMemoryAddress);
					symbTable.put(sourceCode, Integer.toBinaryString(dataMemoryAddress)); 
				}

			}

			/*
			 * The method is called before the source code is
			 * scanned or generated. You have to implement it
			 * to initialize some variables, such as file
			 * descriptors or local variables, in your class.
			 */

			@Override
			void initialization() throws IOException {
				int i;
				String s;
				symbTable = new HashMap<String, String>();
				regTable = new HashMap<String, String>();
				instTable = new HashMap<String, String> ();	

				// instructions
				// 0-9 are Standard
				// 10-17 are immediate based
				// 18-22 are special
				String[] instr = {"add", "sub", "mul", "or", "not", "and", "srl",
						"sll", "lw", "sw", "jr", "halt", "in", "out", "ldi", "bgt",
						"beq", "bgz", "jal", "j", "cxtld", "cxtsv", "lol", "svl",
						"set", "spadd", "ldis"};

				// registers
				String[] regis = {"$0", "$1", "$x", "$y", "$z", "$sp", "$ret0", "$param0",
						"$param1", "$a", "$b", "$save", "$load", "$cmp1", "$cmp2", "$ret_add", 
						"$e1", "$e2", "$e3", "$e4", "$e5", "$e6", "$e7", "$e8",
						"$e9", "$5", "$10"};


				// put items in the hashmap opcodes
				for(i = 0; i < instr.length; i++) {
					s = Integer.toBinaryString(i);
					instTable.put(instr[i], zeroPad(s, 5));
				}

				// put items in the hashmap registers
				for(i = 0; i < regis.length; i++){
					s = Integer.toBinaryString(i);
					regTable.put(regis[i], zeroPad(s, 5));
				}
			}

			/*
			 * You have to implement it for generating the
			 * machine code from an Instruction object.
			 */

			@Override
			String generateCode(Instruction instruction) {
				/* instruction has opcode and operands. 
				 * operands have name and offset. offset is important for formulating the final string.
				 * name is used to find the register that it corresponds to. 
				 */

				if (instruction.operator.equals("WORD") || instruction.operator.equals("FILL"))
				{ return ""; }
				//System.out.println(instruction.operator);
				String inst = instTable.get(instruction.operator);
				int opcode = Integer.parseInt(inst, 2);

				// do i even need to loop through? in wrong place
				String s;


				// instructions
				// 0-9 are Standard
				// 10-17 are immediate based
				// 18-22 are special

				// Standard instruction = 5/4/4/4
				if(opcode < 8)
				{
					//System.err.println(instruction.operator);
					for(int i = 0; i < instruction.operands.length; i++)
					{
						//System.err.println(instruction.operands[i].name);
						inst += regTable.get(instruction.operands[i].name).substring(1,5);
					}
				}
				// store/ load. In order to process offsets
				else if(opcode < 10)
				{
					//System.err.println(instruction.operator);
					inst += regTable.get(instruction.operands[0].name).substring(1,5);
					s = Integer.toBinaryString(instruction.operands[1].offset);
					inst += zeroPad(s, 4);
					inst += regTable.get(instruction.operands[1].name).substring(1,5);
				}
				// jr
				else if(opcode == 10)
				{
					s = "";
					inst += regTable.get(instruction.operands[0].name);
					inst += zeroPad(s, 7);
					
				}
				// halt
				else if(opcode == 11) 
				{
					//System.err.println(instruction.operator);
					for(int i = 0; i < instruction.operands.length; i++)
					{
						//System.err.println(instruction.operands[i].name);
						inst += regTable.get(instruction.operands[i].name).substring(1,5);
					}
				}
				// in
				else if(opcode == 12) 
				{
					inst += regTable.get(instruction.operands[0].name).substring(1,5);
					inst += regTable.get(instruction.operands[1].name).substring(1,5);
					inst += "0000";
				}
				// out
				else if(opcode == 13) 
				{
					inst += "0000";
					inst += regTable.get(instruction.operands[0].name).substring(1,5);
					inst += regTable.get(instruction.operands[1].name).substring(1,5);
				}
				
				// Immediate instruction = 5/12
				else if(opcode < 22)
				{	
					//System.err.println(instruction.operator);
					//System.err.println(instruction.operands[0].name);
					s = instruction.operands[0].name;

					// special case for ldi
					if(instruction.operator.equals("ldi")){

						int val = Integer.valueOf(s);
						//System.err.println(val);
						s = Integer.toBinaryString(val);
					}
					//System.err.println(s);
					inst += zeroPad(s, 12);
					//System.out.println(s);
				}
				// load / save label
				else if(opcode < 24)
				{
					//instruction.print();
					int i = Integer.valueOf(instruction.operands[0].offset) + Integer.valueOf(instruction.operands[0].name);
					//s = Integer.toBinaryString(i);
					s = Integer.toString(i);
					//inst += regTable.get(instruction.operands[0].name).substring(1,5);
					inst += zeroPad(s, 12);	
				}
				// set = 5/5/5/00
				else if(opcode == 24)
				{
					//System.err.println(instruction.operator);
					for(int i = 0; i < instruction.operands.length; i++)
					{
						inst += regTable.get(instruction.operands[i].name);
					}	
					inst += zeros[2];
				}
				// special add = 5/3/4/5
				else if(opcode == 25)
				{
					//System.err.println(instruction.operator);
					String a = regTable.get(instruction.operands[0].name).substring(2,5);
					inst += regTable.get(instruction.operands[0].name).substring(2,5);
					System.out.println(a);
					
					String b = regTable.get(instruction.operands[1].name).substring(1,5);
					inst += regTable.get(instruction.operands[1].name).substring(1,5);
					System.out.println(b);
					
					String c = regTable.get(instruction.operands[2].name);
					inst += regTable.get(instruction.operands[2].name);
					System.out.println(c);
				}
				// load small immediate
				else if(opcode == 26)
				{
					//System.err.println(instruction.operator);

					inst += regTable.get(instruction.operands[0].name);

					s = instruction.operands[1].name;
					int val = Integer.valueOf(s);
					s = Integer.toBinaryString(val);
					inst += zeroPad(s, 7);
				}
				else
				{
					System.out.println("WARNING: Unrecognized Opcode");
				}

				return inst;
			}
		};

		assembler.AssembleCode(arg);
	}

	public static String zeroPad(String s, int length) {
		String t;
		if(s.length() < length) {
			t = zeros[length - s.length()];
			s = t + s;
		}
		return s;
	}
	
	public static boolean isDigit(char a)
	{
		char num[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
		for(int i = 0; i < num.length; i++)
		{
			if(a == num[i]) { return true; }
		}
		return false;
	}
}

